package cn.funeralobjects.ams.service.impl;

import cn.funeralobjects.ams.entity.EProduct;
import cn.funeralobjects.ams.entity.EProductApi;
import cn.funeralobjects.ams.entity.ERamRoleInfo;
import cn.funeralobjects.ams.model.*;
import cn.funeralobjects.ams.service.*;
import cn.funeralobjects.common.model.KeyValue;
import cn.funeralobjects.common.service.annotataion.CommonService;
import cn.funeralobjects.common.service.exception.LackDataException;
import cn.funeralobjects.common.service.exception.OperationException;
import cn.funeralobjects.util.annotation.ArgHasLength;
import cn.funeralobjects.util.annotation.ArgNotZero;
import cn.funeralobjects.util.annotation.AssertArg;
import cn.funeralobjects.utils.aliyun.core.AliyunClientFactory;
import cn.funeralobjects.utils.aliyun.core.model.AliyunAction;
import cn.funeralobjects.utils.aliyun.core.model.AliyunClient;
import cn.funeralobjects.utils.aliyun.core.model.AliyunResponse;
import cn.funeralobjects.utils.aliyun.sts.StsAction;
import cn.funeralobjects.utils.aliyun.sts.StsProduct;
import cn.funeralobjects.utils.aliyun.sts.model.AssumeRoleData;
import cn.funeralobjects.utils.aliyun.sts.model.AssumeRoleParam;
import com.aliyuncs.exceptions.ClientException;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

/**
 * @author FuneralObjects
 * Create date: 2020/6/7 6:11 PM
 */
@CommonService
public class StsAccessKeyServiceImpl implements StsAccessKeyService {

    private EProduct stsProduct;
    private ConcurrentMap<AliyunAction, EProductApi> stsActionMap;

    @Resource
    private ProductResService productResService;
    @Resource
    private ProductApiAccessService productApiAccessService;
    @Resource
    private ProductApiService productApiService;
    @Resource
    private RamRoleService ramRoleService;
    @Resource
    private ProductService productService;

    @PostConstruct
    public void initProduct() {
        stsProduct = productService.findByCode(StsProduct.CODE)
                .orElseGet(() -> productService.add(new Product().setCode(StsProduct.CODE).setDescription(StsProduct.DESCRIPTION)));

        stsActionMap = StsProduct.ACTIONS
                .stream()
                .map(action -> {
                    EProductApi api = productApiService.findOne(stsProduct.getId(), action.getAction(), action.getVersion())
                            .orElseGet(() -> productApiService.add(new ProductApi()
                                    .setAction(action.getAction())
                                    .setDomain(action.getDomain())
                                    .setMethod(action.getMethod())
                                    .setName(action.getName())
                                    .setProtocol(action.getProtocol())
                                    .setVersion(action.getVersion())
                                    .setProductId(stsProduct.getId())
                            ));
                    return new KeyValue<>(action, api);
                }).collect(Collectors.toConcurrentMap(KeyValue::getKey, KeyValue::getValue));
    }


//    @AssertArg
//    @Override
//    public StsAccessKey createStsAccessKeyByRole(@ArgHasLength String roleSessionName, @ArgNotZero Integer ramRoleId, @ArgNotZero Integer prodResId,Policy policy) {
//        return createStsAccessKeyByRoleInner(roleSessionName, ramRoleId, prodResId, policy, null);
//    }

    @AssertArg
    @Override
    public StsAccessKey createStsAccessKeyByRole(@ArgHasLength String roleSessionName, @ArgNotZero Integer ramRoleId, @ArgNotZero Integer prodResId, Policy policy, Long durationTime) {
        return createStsAccessKeyByRoleInner(roleSessionName, ramRoleId, prodResId, policy, durationTime);
    }

    private StsAccessKey createStsAccessKeyByRoleInner(String roleSessionName, Integer ramRoleId, Integer prodResId, Policy policy, Long durationTime) {
        ProductApiAccessDto accessDto = productApiAccessService.getProductApiAccessKey(prodResId, StsAction.ASSUME_ROLE.getAction(), StsAction.ASSUME_ROLE.getVersion()).orElseThrow(() -> new LackDataException(String.format("The AccessKey does't exists of ProductRes[%s]", prodResId), productResService.createDataCode("id", String.valueOf(prodResId))));
        AliyunClient client = AliyunClientFactory.builder()
                .setAccessKeySecret(accessDto.getAccessKeySecret())
                .setAccessKeyId(accessDto.getAccessKeyId())
                .build(accessDto.getRegionId()).buildClient(StsAction.ASSUME_ROLE);
        ERamRoleInfo ramRoleInfo = ramRoleService.getRamInfoById(ramRoleId).orElseThrow(() -> new LackDataException(String.format("The ramRole[%s] doesn't exists.", ramRoleId), ramRoleService.createDataCode("id", String.valueOf(ramRoleId))));
        String accountId = ramRoleInfo.getArn().split(":")[3];
        String roleName = ramRoleInfo.getRoleName();
        try {
            AliyunResponse<AssumeRoleData> response = client.doAction(durationTime == null ? new AssumeRoleParam(accountId, roleName, roleSessionName,
                    policy == null ?
                            null :
                            new AssumeRoleParam.Policy()
                                    .setVersion(policy.getVersion())
                                    .setStatement(policy.getStatement() == null ? null :
                                            policy.getStatement()
                                                    .stream()
                                                    .map(s -> new AssumeRoleParam.Statement().setAction(s.getAction()).setEffect(s.getEffect()).setResource(s.getResource()))
                                                    .collect(Collectors.toList())))
                    : new AssumeRoleParam(accountId, roleName, roleSessionName, null, durationTime));
            AssumeRoleData.Credentials credentials = response.getData().getCredentials();
            return new StsAccessKey().setAccessKeyId(credentials.getAccessKeyId())
                    .setAccessKeySecret(credentials.getAccessKeySecret())
                    .setExpiration(credentials.getExpiration())
                    .setSecurityToken(credentials.getSecurityToken());
        } catch (ClientException e) {
            throw new OperationException("The aliyun client error", e);
        }
    }

}
